package com.duo.enums.minio;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.duo.result.BaseResult;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Encoder;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.rowset.serial.SerialException;
import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @Author: Xrx
 * @Description:
 * @CreateTime: 2023/7/7 0:44
 */
@Data
@Component
public class MinIOConfig {

    @Value("${minio.address}")
    private String address;

    @Value("${minio.accessKey}")
    private String accessKey;

    @Value("${minio.secretKey}")
    private String secretKey;

    @Value("${minio.bucketName}")
    private String bucketName;

    public MinioClient getMinioClient() {
        try {
            return MinioClient.builder()
                    .endpoint(address)
                    .credentials(accessKey, secretKey)
                    .build();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 检查存储桶是否存在
     * @param bucketName 存储桶名称
     * @return
     */
    public boolean bucketExists(MinioClient minioClient, String bucketName) {
        boolean flag = false;
        try {
            flag = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (flag) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return false;
    }

    /**
     * 创建存储桶
     * @param bucketName 存储桶名称
     */
    public boolean makeBucket(MinioClient minioClient, String bucketName) {
        try {
            boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            //存储桶不存在则创建存储桶
            if (!found) {
                // 如果不存在创建桶
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 上传文件
     * @param file 上传文件
     * @return 成功则返回文件名，失败返回空
     */
    public Object uploadFile(MinioClient minioClient, MultipartFile file) throws SerialException {
        //创建存储桶
        boolean createFlag = makeBucket(minioClient, bucketName);
        //创建存储桶失败
        if (createFlag == false) {
            throw new SerialException("创建存储桶失败");
        }
        try {
            String originalFilename = file.getOriginalFilename();
            int pointIndex = originalFilename.lastIndexOf(".");
            //得到文件流
            InputStream inputStream = file.getInputStream();
            //保证文件不重名(并且没有特殊字符)
            String fileName = bucketName + DateUtil.format(new Date(), "_yyyy-MM-dd-HH-mm-ss") 
                    + (pointIndex > -1 ? originalFilename.substring(pointIndex) : "");
            minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(fileName).contentType(ViewContentType.getContentType(fileName)).stream(inputStream, inputStream.available(), -1).build());

            inputStream.close();
            return BaseResult.success(fileName);
        } catch (Exception e) {
            e.printStackTrace();
            return BaseResult.error("文件上传失败");
        }
    }

    /**
     * 下载文件
     * @param originalName 文件路径
     */

    public InputStream downloadFile(MinioClient minioClient, String originalName, HttpServletResponse response) {
        try {
            // 2. 获取对象的InputStream,并保存为文件
            InputStream file = minioClient.getObject(
                    GetObjectArgs.builder().bucket(bucketName).object(originalName).build());
            String filename = new String(originalName.getBytes("ISO8859-1"), StandardCharsets.UTF_8);
            if (StrUtil.isNotBlank(originalName)) {
                filename = originalName;
            }
            response.setHeader("Content-Disposition", "attachment;filename=" + filename);
            ServletOutputStream servletOutputStream = response.getOutputStream();
            int len;
            byte[] buffer = new byte[1024];
            while ((len = file.read(buffer)) > 0) {
                servletOutputStream.write(buffer, 0, len);
            }
            servletOutputStream.flush();
            file.close();
            servletOutputStream.close();
            return file;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 删除文件
     * @param fileName 文件路径
     * @return
     */
    public boolean deleteFile(MinioClient minioClient, String fileName) {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
    /**
     * Description 生成预览链接，最大7天有效期；
     * 如果想永久有效，在 minio 控制台设置仓库访问规则总几率
     * Create by Mr.Fang
     * @param object      文件名称
     * @param contentType 预览类型 image/gif", "image/jpeg", "image/jpg", "image/png", "application/pdf
     * @Params
     **/
    /**
     * @description:获取文件预览接口
     * @date: 2022/8/18 9:44
     * @param: fileName: 文件名
     * @Param: bucketName: 桶名
     * @Param: previewExpiry: 预览到期时间（小时）
     * @return: java.lang.String
     **/
    public String getPreviewUrl(MinioClient minioClient, String fileName, Integer previewExpiry) {
        if (StrUtil.isNotBlank(fileName)) {
            try {
                minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(fileName).build());
                if (null != previewExpiry) {
                    return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(bucketName)
                            .object(fileName)
                            .expiry(previewExpiry, TimeUnit.HOURS)
                            .build());
                } else {
                    return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(bucketName)
                            .object(fileName)
                            .build());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 查询所有桶文件
     * @return
     */
    public List<Bucket> getListBuckets(MinioClient minioClient) {
        try {
            return minioClient.listBuckets();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Collections.emptyList();
    }

    /**
     * 批量删除文件
     * @param objects 对象名称
     * @return boolean
     */
    public List<DeleteError> deleteObject(MinioClient minioClient, List<String> objects) {
        List<DeleteError> deleteErrors = new ArrayList<>();
        List<DeleteObject> deleteObjects = objects.stream().map(value -> new DeleteObject(value)).collect(Collectors.toList());
        Iterable<Result<DeleteError>> results =
                minioClient.removeObjects(
                        RemoveObjectsArgs
                                .builder()
                                .bucket(bucketName)
                                .objects(deleteObjects)
                                .build());
        try {
            for (Result<DeleteError> result : results) {
                DeleteError error = result.get();
                deleteErrors.add(error);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return deleteErrors;
    }

    /**
     * 获取单个桶中的所有文件对象名称
     * @param bucket 桶名称
     * @return {@link List}<{@link String}>
     */
    public List<String> getBucketObjectName(MinioClient minioClient, String bucket) {
        boolean exsit = makeBucket(minioClient, bucket);
        if (exsit) {
            List<String> listObjetcName = new ArrayList<>();
            try {
                Iterable<Result<Item>> myObjects = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucket).build());
                for (Result<Item> result : myObjects) {
                    Item item = result.get();
                    listObjetcName.add(item.objectName());
                }
                return listObjetcName;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 获取某个桶下某个对象的URL
     * @param objectName 对象名 (文件夹名 )
     * @return
     */
    public String getBucketObject(MinioClient minioClient, String objectName) throws Exception {
        // 删除之前先检查`my-bucket`是否存在。
        boolean found = makeBucket(minioClient, bucketName);
        if (found) {
            final GetObjectResponse object = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
            return object.object();
        }
        return "";
    }

    /**
     * @description: 批量下载
     * @param: filenames: 多个文件名称
     * @Param: zip: 压缩包名称
     * @Param: res: 响应对象
     * @return: void
     **/
    public void batchDownload(MinioClient minioClient, List<String> filenames, String zip, HttpServletResponse res, HttpServletRequest req) {
        try {
            BucketExistsArgs bucketArgs = BucketExistsArgs.builder().bucket(bucketName).build();
            boolean bucketExists = minioClient.bucketExists(bucketArgs);
            BufferedOutputStream bos = null;
            res.reset();
            bos = new BufferedOutputStream(res.getOutputStream());
            ZipOutputStream out = new ZipOutputStream(bos);
            res.setHeader("Access-Control-Allow-Origin", "*");
            for (int i = 0; i < filenames.size(); i++) {
                GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName)
                        .object(filenames.get(i)).build();
                InputStream object = minioClient.getObject(objectArgs);
                byte buf[] = new byte[1024];
                int length = 0;
                res.setCharacterEncoding("utf-8");
                res.setContentType("application/force-download");// 设置强制下载不打开
                res.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
                res.setHeader("Content-Disposition", "attachment;filename=" + filenameEncoding(zip, req) + ".zip");
                out.putNextEntry(new ZipEntry(filenames.get(i)));
                while ((length = object.read(buf)) > 0) {
                    out.write(buf, 0, length);
                }
            }
            out.close();
            bos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 设置不同浏览器编码
     * @param filename 文件名称
     * @param request  请求对象
     */
    public static String filenameEncoding(String filename, HttpServletRequest request) throws UnsupportedEncodingException {
        // 获得请求头中的User-Agent
        String agent = request.getHeader("User-Agent");
        // 根据不同的客户端进行不同的编码

        if (agent.contains("MSIE")) {
            // IE浏览器
            filename = URLEncoder.encode(filename, "utf-8");
        } else if (agent.contains("Firefox")) {
            // 火狐浏览器
            BASE64Encoder base64Encoder = new BASE64Encoder();
            filename = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?=";
        } else {
            // 其它浏览器
            filename = URLEncoder.encode(filename, "utf-8");
        }
        return filename;
    }
}
